/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020.
 * Description: kbox regions extend
 * Author: chenzefeng
 * Create: 2020-7-2
 */

#include "kbox_dynamic.h"
#include <linux/kernel.h>
#include <linux/string.h>
#include "kbox_core.h"

/*
 *    --------------    kbox_memory_list.fs_head
 *   |              |
 *   |              |
 *   |--------------|   kbox_memory_list.region_table
 *   |              |
 *   |     ...      |
 *   |              |
 *   |              |
 *   |--------------|   dym_head->data_offset  =
 *   |              |   region_table[63]->data_offset + region_table[63]->size
 *   |     ...      |
 *   |--------------|
 *   |              |
 *   |              |
 *   |--------------|   kbox_memory_list.dym_region_table[i] = dym_region_table - (i + 1)
 *   |              |
 *   |--------------|   dym_region_table = dym_head = end - sizeof(struct dym_region_head)
 *   |              |
 *    --------------   end
 */

static inline kbox_region_desc *get_dym_region_desc(const u32 id)
{
	return (kbox_region_desc *)((char *)kbox_memory_list.dym_region_table -
			(size_t)id * kbox_region_desc_size());
}

static inline kbox_region_desc *get_dym_last_region(void)
{
	return get_dym_region_desc(kbox_memory_list.dym_head->file_count);
}

void kbox_memory_list_dym_init(void)
{
	kbox_memory_list.dym_head = (struct dym_region_head *)(kbox_memory_list.memory_head +
		kbox_memory_list.memory_size - sizeof(struct dym_region_head));
	kbox_memory_list.dym_region_table = (kbox_region_desc *) kbox_memory_list.dym_head;
}

bool check_kbox_dym_region_desc(char *warning_str, int *warning_str_len)
{
	u32 i, data_offset;
	u32 region_desc_size = kbox_region_desc_size();
	kbox_region_desc *region = NULL;

	if (kbox_memory_list.fs_head->basic.reserved1 == 0)
		return false;

	data_offset = kbox_memory_list.dym_head->data_offset;
	region = kbox_memory_list.dym_region_table;
	for (i = 1; i <= kbox_memory_list.dym_head->file_count; i++) {
		region = (kbox_region_desc *)((char *)region - region_desc_size);
		if (check_kbox_region_desc_single(region, data_offset, warning_str, warning_str_len))
			return true;
		data_offset += get_desc_size(region);
	}
	if (data_offset >
	    kbox_memory_list.memory_size - kbox_memory_list.dym_head->page_num * PAGE_SIZE) {
		*warning_str_len = snprintf(warning_str, KBOX_WARNNING_LEN,
			"%s 's offset(%u), size(%u), memory size(%lu)\n",
			region->name, get_desc_offset(region), get_desc_size(region),
			kbox_memory_list.memory_size);
		return true;
	}

	return false;
}

static bool is_memory_enough(u32 create_size)
{
	unsigned int used_size = kbox_memory_list.fs_head->basic.used_size;
	unsigned int unused_size = kbox_memory_list.memory_size - used_size;

	if (unused_size <= PAGE_SIZE || create_size > unused_size - PAGE_SIZE) {
		error("NO enough memory for extend region table\n");
		return false;
	}
	return true;
}

static kbox_region_desc *create_desc_table(u32 create_size)
{
	struct dym_region_head *new = NULL;
	u32 region_desc_size = kbox_region_desc_size();
	kbox_region_desc *region = NULL;

	if (!is_memory_enough(create_size))
		return NULL;

	/* the reserved1 set to 1 mean have a dynamic kbox region */
	kbox_memory_list.fs_head->basic.reserved1 = 1;
	kbox_memory_list.fs_head->basic.used_size += PAGE_SIZE;

	new = kbox_memory_list.dym_head;

	new->file_count = 1;
	new->file_support = (PAGE_SIZE - sizeof(struct dym_region_head)) / region_desc_size;
	new->page_num = 1;
	new->file_desc_table_checksum = 0;

	region = get_last_region_desc();
	new->data_offset = get_desc_offset(region) + get_desc_size(region);

	return get_dym_last_region();
}

static kbox_region_desc *extend_desc_table(u32 create_size)
{
	u32 region_desc_size = kbox_region_desc_size();

	if (!is_memory_enough(create_size))
		return NULL;

	kbox_memory_list.fs_head->basic.used_size += PAGE_SIZE;

	kbox_memory_list.dym_head->page_num++;
	kbox_memory_list.dym_head->file_support = (kbox_memory_list.dym_head->page_num * PAGE_SIZE -
		sizeof(struct dym_region_head)) / region_desc_size;
	kbox_memory_list.dym_head->file_count++;
	return get_dym_last_region();
}

static kbox_region_desc *kbox_dym_region_desc_empty_get(u32 create_size)
{
	unsigned int reserved1 = kbox_memory_list.fs_head->basic.reserved1;

	if (reserved1 == 0)
		return create_desc_table(create_size);

	if (kbox_memory_list.dym_head->file_count < kbox_memory_list.dym_head->file_support) {
		kbox_memory_list.dym_head->file_count++;
		return get_dym_last_region();
	}

	return extend_desc_table(create_size);
}

int create_new_kbox_dym_region(kbox_region_desc **plogdes,
	const char *create_name, u32 create_size, unsigned char flags)
{
	kbox_region_desc *region_desc = NULL;
	u32 offset;
	int ret;

	region_desc = kbox_dym_region_desc_empty_get(create_size);
	if (region_desc == NULL) {
		error("create region: no enough memory\n");
		return -ENOMEM;
	}

	/* fill region desc */
	ret = snprintf(region_desc->name, KBOX_REGION_NAME_LEN, "%s", create_name);
	if (ret >= KBOX_REGION_NAME_LEN) {
		error("create region: name %s too long, %zu > %d\n",
			create_name, strlen(create_name), KBOX_REGION_NAME_LEN);
		return -EINVAL;
	}
	set_desc_size(region_desc, PAGE_ALIGN(create_size));
	offset = kbox_memory_list.fs_head->basic.used_size -
		 kbox_memory_list.dym_head->page_num * PAGE_SIZE;
	set_desc_offset(region_desc, offset);
	region_desc->index = kbox_memory_list.fs_head->file_support +
		kbox_memory_list.dym_head->file_count;
	set_desc_log_start(region_desc, 0);
	set_desc_log_end(region_desc, 0);
	set_desc_real_size(region_desc, 0);
	region_desc->flags = flags;

	*plogdes = region_desc;
	kbox_memory_list.fs_head->basic.used_size += get_desc_size(region_desc);
	update_checksum();
	return 1;
}

kbox_region_desc *kbox_dym_region_desc_find(const char *name)
{
	struct dym_region_head *dym_head = kbox_memory_list.dym_head;
	int i;
	kbox_region_desc *region = NULL;

	if (kbox_memory_list.fs_head->basic.reserved1 == 0)
		return NULL;

	if (name == NULL || dym_head->file_count > dym_head->file_support)
		return NULL;

	for (i = 1; i <= dym_head->file_count; i++) {
		region = get_dym_region_desc(i);
		if (!strncmp(name, region->name, KBOX_REGION_NAME_LEN))
			return region;
	}

	return NULL;
}

int create_dym_history_regions(void)
{
	kbox_region_desc *region;
	int i;
	int ret;

	if (kbox_memory_list.fs_head->basic.reserved1 == 0)
		return 0;

	for (i = 1; i <= kbox_memory_list.dym_head->file_count; i++) {
		region = get_dym_region_desc(i);

		ret = create_region_desc(region);
		if (ret != 0)
			return ret;
	}

	return 0;
}

void kbox_do_delete_dym_region(kbox_region_desc *region_desc)
{
	kbox_memory_list.dym_head->file_count--;
	kbox_memory_list.fs_head->basic.used_size -= get_desc_size(region_desc);
	memset(region_desc, 0, kbox_region_desc_size());
}
